<?php
if (class_exists('Memcache'))
{
	class NICED_Memcache extends Memcache 
	{
        public function get( $k )
        {
            if( ! is_scalar( $k ) && ! is_array( $k ) )
            {
                $e = new Exception('invalid memcache key');
                syslog(LOG_INFO, NICED.' ' . $e->__toString());
                return false;
            }

            $niced_log_level = SC::get("board_config.niced_log_level");
            $key = self::getKey($k);

            $res = parent::get($key);
            if(is_array($key) && !is_array($res)) 
            {
                $res = array();
            }

            NICED_LOG($niced_log_level, $res, NICED." get mc k $k: key ".$key.", rs ");

            return $res;
        }

        public function set($k, $v, $flag = NULL, $expire = NULL)
        {
            //$expire = (int)$expire;
            $key = self::getKey($k);
            $bool = parent::set($key, $v, $flag, $expire);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." set mc k ".$k.": key ".$key.", rs ".(int)$bool." expire ".$expire);

            return $bool;
        }

        public function delete($k)
        {
            $key = self::getKey($k);
            $bool = parent::delete($key);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." del mc key ".$k.": key ".$key.", rs ".(int)$bool);

            return $bool;
        }

        public function increment($k, $v = 1)
        {
            $key = self::getKey($k);
            $bool = parent::increment($key, $v);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." increment mc k ".$k.": key ".$key.", rs ".(int)$bool);

            return $bool;
        }

        public function decrement($k, $v = 1)
        {
            $key = self::getKey($k);
            $bool = parent::decrement($key, $v);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." decrement mc k ".$k.": key ".$key.", rs ".(int)$bool);

            return $bool;
        }

        public function replace($k, $v, $flag = NULL, $expire = NULL)
        {
            $key = self::getKey($k);
            $bool = parent::replace($key, $v, $flag, $expire);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." replace mc k ".$k.": key ".$key.", rs ".(int)$bool);

            return $bool;
        }

        public function add($k, $v, $flag = NULL, $expire = NULL)
        {

            $key = self::getKey($k);
            $bool = parent::add($key, $v, $flag, $expire);

            NICED_LOG(SC::get("board_config.niced_log_level"), NICED." add mc k ".$k.": key ".$key.", rs ".(int)$bool);

            return $bool;
        }

        private static function getKey($k)
        {
            $namespace = SC::get("board_config.memcache.namespace");

            if ($namespace)
            {
                $k = $namespace."_".$k;
            }

            return $k;
        }

        public function close()
        {

        }

        public function __destruct()
        {

        }
    } //end class

    class NICED_NamespacedMemcache extends NICED_Memcache
    {
        public function __construct(Memcache $memcache, $namespace)
        {
            $this->core      = $memcache;
            $this->namespace = $namespace;
        }

        private function __buildKey($key)
        {
            return $this->namespace."_".$key;
        }

        public function set($key, $value, $flag = NULL, $expire = NULL)
        {
            return $this->core->set($this->__buildKey($key), $value, $flag, $expire); 
        }

        public function increment($key, $value = 1)
        { 
            return $this->core->increment($this->__buildKey($key), $value); 
        }

        public function replace($key, $value, $flag = NULL, $expire = NULL)
        { 
            return $this->core->replace($this->__buildKey($key), $value, $flag, $expire); 
        }

        public function add($key, $value, $flag = NULL, $expire = NULL)
        { 
            return $this->core->add($this->__buildKey($key), $value, $flag, $expire); 
        }

        public function decrement($key, $value = 1)
        { 
            return $this->core->decrement($this->__buildKey($key), $value);
        }

        public function delete($key, $timeout = NULL) 
        { 
            return $this->core->delete($this->__buildKey($key), $timeout);
        }
        
        public function get($key)
        { 
            if(is_scalar($key)) 
            {
                return $this->core->get($this->__buildKey($key));
            }

            if(!is_array($key)) 
            {
                return false;
            }

            $keys = array();
            foreach($key as $k) 
            {
                $keys[] = $this->__buildKey($k);
            }

            $result = $this->core->get($keys);
            if(!is_array($result)) 
            {
                return $result;
            }

            $results = array();
            $len = strlen( $this->namespace);

            foreach( $result as $k => $v) 
            {
                $results[substr($k, $len)] = $v;
            }

            return $results;
        }

        public function flush()
        { 
            return false; 
        }


        public function __call($method, $args)
        { 
            return call_user_func_array( array( $this->core, $method), $args ); 
        }

        //public function __call($method, $arg)
        //{
        //    $arg[0] = $this->namespace.$arg[0];
        //    $arg_str = implode(",", $arg);
        //    eval("\$rt = \$this->core->\$method(\$arg_str);");

        //    return $rt;
        //}
    }


    class NICED_ReplicaMemcache extends NICED_Memcache 
    {
        private $replicas = array();
        private $expiry;

        public function __construct(Memcache $core, $replicas = NULL)
        { 
            $replicas = ($replicas && is_numeric($replicas) && $replicas > 1) ? intval($replicas) : 3;

            for( $i = 0; $i < $replicas; $i++)
            {
                $this->replicas[] = new NamespacedMemcache($core, '_replica_' . $replicas . '_' . $i . '.');
            }

            shuffle($this->replicas);
            $this->expiry = new NamespacedMemcache($core, '_replica_exp_' . $replicas);
        }

        public function get($key)
        {
            $keys = is_scalar($key) ? array($key) : $key;

            if(!is_array($keys)) 
            {
                return false;
            }

            $matches = array_fill_keys($keys, NULL);

            if(!$this->expiry instanceof Memcache) 
            {
                $e = new Exception('invalid-expiry-obj');
                syslog(LOG_INFO, '[NICED] ' . $e->__toString());
                return is_array( $key ) ? array() : false;
            }

            $unexpired = $this->expiry->get($keys);

            $expired = array();
            foreach ($keys as $k) 
            {
                if (!isset($unexpired[$k])) 
                {
                    $expired[$k] = NULL;
                }
            }

            foreach($this->replicas as $replica)
            {
                $keys_left = array_keys($matches, NULL);

                if(count($keys_left) < 1) 
                {
                    break;
                }

                $res = $replica->get($keys_left);

                if($res === false) 
                {
                    continue;
                }

                foreach($res as $k => $v) 
                {
                    $matches[$k] = $v;
                }
            }

            foreach(array_keys($matches, NULL) as $k) 
            {
                unset($matches[$k]);
            }

            if(is_array($expired))
            {
                foreach($expired as $k=>$v)
                {
                    if(!isset( $matches[$k])) 
                    {
                        continue;
                    }

                    if(!$this->expiry->add($k, 1, 0, 60)) 
                    {
                        continue;
                    }

                    unset($matches[$k]);
                }
            }

            if(is_scalar($key)) 
            {
                return isset($matches[$key]) ? $matches[ $key ] : false;
            }

            return $matches;
        }

        private function __store($k, $v, $flag = NULL, $expire = 1000)
        {
            $res = false;

            $this->expiry->set($k, 1, 0, $expire);//$k, $v, compress, expire

            foreach($this->replicas as $replica)
            {
                $r = $replica->set($k, $v, $flag, $expire + self::REPLICA_ADDED_EXPIRE);
                if($r) 
                {
                    $res = $r;
                }
            }

            return $res;
        }
        
        public function set($k, $v, $flag = NULL, $expire = NULL ){
            $res = FALSE;
            if( ! $expire ) $expire = 10000;
            $this->expiry->set($k, 1, 0, $expire );
            foreach( $this->replicas as $replica){
                $r = $replica->set($k, $v, $flag, $expire + 300 );
                if( $r ) $res = $r;
            }
            return $res;
        }
        
        function delete( $k ){
            $res = FALSE;
            foreach( $this->replicas as $replica) {
                $r = $replica->delete( $k );
                if( $r ) $res = $r;
            }
            $this->expiry->delete($k);
            return $res;
        }   
        
        function add($k, $v, $flag = NULL, $expire = NULL ){
            $res = FALSE;
            if( ! $expire ) $expire = 10000;
            if( ! $this->expiry->add($k, 1, 0, $expire ) ) return FALSE;
            foreach( $this->replicas as $replica){
                $r = $replica->set($k, $v, $flag, $expire + 300 );
                if( $r ) $res = $r;
            }
            return $res;
        }
        
        function replace($k, $v, $flag = NULL, $expire = NULL ){
            $res = FALSE;
            if( ! $expire ) $expire = 10000;
            if( ! $this->expiry->replace($k, 1, 0, $expire ) ) return FALSE;
            foreach( $this->replicas as $replica){
                $r = $replica->set($k, $v, $flag, $expire + 300 );
                if( $r ) $res = $r;
            }
            return $res;
        }
        
        
        // Before propogating data to replicas after incrementing one,
        // check to see if we're getting the pre-increment or post-increment value
        function increment($k, $v = 1){
        
            $first = TRUE;
            foreach( $this->replicas as $replica){
                if( $first ){
                    $r = $replica->increment($k, $v);
                    if( $r === FALSE ) continue;
                    $first = FALSE;
                    $v = $r;
                } else {
                    $replica->set($k, $v, 0, 10000);
                }
            }
            return $v;
        }
        
        function decrement($k, $v = 1){
            $first = TRUE;
            foreach( $this->replicas as $replica){
                if( $first ){
                    $r = $replica->decrement($k, $v);
                    if( $r === FALSE ) continue;
                    $first = FALSE;
                    $v = $r;
                } else {
                    $replica->set($k, $v, 0, 10000);
                }
            }
            return $v;

        }
    }

}//end class
